/*
 * ID3.java
 */
package eDoktor.kararMekanizmasi.kararAgaci;

import eDoktor.hastalikGrubu.Cevap;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.*;

/**
 * ID3 algoritmasının kullanılmasını sağlayan sınıf
 * @author Onuralp
 */
public class ID3 {
    private int oznitelikNumarasi;
    private String []oznitelikIsimleri;
    private Vector[] etkiAlanlari;
    private Dugum kok;
    
    /**
     * ID3 sınıfının yapılandırıcısı
     */
    public ID3() {
        oznitelikNumarasi = 0;
        kok = new Dugum();
    }

    /**
     * ID3 sınıfının degiskenlerine erişmeyi sağlayan fonksiyonlar 
     */
    
    public Vector[] getEtkiAlanlari() {
        return etkiAlanlari;
    }

    public void setEtkiAlanlari(Vector[] etkiAlanlari) {
        this.etkiAlanlari = etkiAlanlari;
    }

    public Dugum getKok() {
        return kok;
    }

    public void setKok(Dugum kok) {
        this.kok = kok;
    }

    public String[] getOznitelikIsimleri() {
        return oznitelikIsimleri;
    }

    public void setOznitelikIsimleri(String[] oznitelikIsimleri) {
        this.oznitelikIsimleri = oznitelikIsimleri;
    }

    public int getOznitelikNumarasi() {
        return oznitelikNumarasi;
    }

    public void setOznitelikNumarasi(int oznitelikNumarasi) {
        this.oznitelikNumarasi = oznitelikNumarasi;
    }
    
    /**
     * Sembol değerlerini getir.
     * @param nitelik Nitelik
     * @param sembol Sembol
     * @return Sembol değeri
     */
    public int sembolDegeriniGetir(int nitelik, String sembol) {
        int i = etkiAlanlari[nitelik].indexOf(sembol);
        if (i < 0) {
            etkiAlanlari[nitelik].addElement(sembol);
            return etkiAlanlari[nitelik].size()-1;
        }
        return i;
    }
    
    /**
     * Bütün değerleri getir.
     * @param veriler Veriler
     * @param nitelik Nitelik
     * @return Değer kümesi
     */
    public int[] butunDegerleriGetir(Vector veriler, int nitelik) {
        Vector degerler = new Vector();
        int elemanSayisi = veriler.size();
        for (int i=0; i< elemanSayisi; i++) {
            Veri nokta = (Veri)veriler.elementAt(i);
            String sembol = (String)etkiAlanlari[nitelik].elementAt(nokta.getOznitelikleri(nitelik));
            int indeks = degerler.indexOf(sembol);
            if (indeks < 0) {
                degerler.addElement(sembol);
            }
        }
        int []dizi = new int[degerler.size()];
        for (int i=0; i< dizi.length; i++) {
            String symbol = (String)degerler.elementAt(i);
            dizi[i] = etkiAlanlari[nitelik].indexOf(symbol);
        }
        degerler = null;
        return dizi;
    }    
    
    /**
     * Alt kümeleri getir.
     * @param veriler Veriler
     * @param nitelik Nitelik
     * @param deger Değer
     * @return Alt küme
     */
    public Vector altKumeleriGetir(Vector veriler, int nitelik, int deger) {
        Vector altKume = new Vector();
        int elemanSayisi = veriler.size();
        for (int i=0; i< elemanSayisi; i++) {
            Veri nokta = (Veri)veriler.elementAt(i);
            if (nokta.getOznitelikleri(nitelik) == deger) {
                altKume.addElement(nokta);
            }
        }
        return altKume;
    }  
    
    /**
     * Düğüm noktalarını hesapla.
     * @param veriler Veriler 
     * @return Hesaplanan düğüm noktası
     */
    public double dugumNoktalariniHesapla(Vector veriler) {
        int veriNumarasi = veriler.size();
        if (veriNumarasi == 0) {
            return 0;
        }
        int nitelik = oznitelikNumarasi-1;
        int nitelikDegeri = etkiAlanlari[nitelik].size();
        double topla = 0;
        for (int i=0; i< nitelikDegeri; i++) {
            int sayac = 0;
            for (int j=0; j<veriNumarasi; j++) {
                Veri point = (Veri)veriler.elementAt(j);
                if (point.getOznitelikleri(nitelik) == i) {
                    sayac++;
                }
            }
            double Olasilik = 1.*sayac/veriNumarasi;
            if (sayac > 0) {
                topla += -Olasilik*Math.log(Olasilik);
            }
        }
        return topla;
    }
    
    /**
     * Ayrıştırmayı kontrol et.
     * @param dugum Düğüm
     * @param nitelik Nitelik
     * @return İşlem tamam mı?
     */
    public boolean ayristirmayiKontrolEt(Dugum dugum, int nitelik) {
        if (dugum.getAltDugum() != null) {
            if (dugum.getAyrismaOzniteligi() == nitelik ) {
                return true;
            }
        }
        if (dugum.getUstDugum() == null) { 
            return false;
        }
        return ayristirmayiKontrolEt(dugum.getUstDugum(), nitelik);
    }
    
    /**
     * Ayrıştırma işlemini gerçekleştir.
     * @param dugum Düğüm
     */
    public void ayristirma(Dugum dugum) {
        System.out.println("Ayrıştırma işlemi: Kök düğüm -> " + dugum.getAyrismaDegeri());
        double enIyiDugumNoktasi=0;
        boolean secilen=false;
        int secilenNitelik=0;
        int veriNumarasi = dugum.getVeriKumesi().size();
        int girilenNiteliklerinNumarasi = oznitelikNumarasi-1;
        dugum.setDugunNoktalari(dugumNoktalariniHesapla(dugum.getVeriKumesi()));
        if (dugum.getDugunNoktalari() == 0) {
            return;
        }
        for (int i=0; i<girilenNiteliklerinNumarasi; i++) {
            int degerNumarasi = etkiAlanlari[i].size();
            if (ayristirmayiKontrolEt(dugum, i)) {
                continue;
            }
            double ortalamaDugumNoktasi = 0;
            for (int j=0; j< degerNumarasi; j++) {
                Vector altKume = altKumeleriGetir(dugum.getVeriKumesi(), i, j);
                if (altKume.size() == 0) { 
                    continue;
                }
                double altDugumNoktasi = dugumNoktalariniHesapla(altKume);
                ortalamaDugumNoktasi += altDugumNoktasi * altKume.size();
            }
            ortalamaDugumNoktasi = ortalamaDugumNoktasi / veriNumarasi;
            if (secilen == false) {
                secilen = true;
                enIyiDugumNoktasi = ortalamaDugumNoktasi;
                secilenNitelik = i;
            }
            else {
                if (ortalamaDugumNoktasi < enIyiDugumNoktasi) {
                    secilen = true;
                    enIyiDugumNoktasi = ortalamaDugumNoktasi;
                    secilenNitelik = i;
                }
            }
        }
        if (secilen == false) {
            return;
        }
        int degerNumarasi = etkiAlanlari[secilenNitelik].size();
        dugum.setAyrismaOzniteligi(secilenNitelik);
        dugum.setAltDugum(new Dugum[degerNumarasi]);
        for (int j=0; j<degerNumarasi; j++) {
            dugum.setAltDugum(new Dugum(), j);
            dugum.getAltDugum(j).setUstDugum(dugum);
            dugum.getAltDugum(j).setVeriKumesi(altKumeleriGetir(dugum.getVeriKumesi(), secilenNitelik, j));
            dugum.getAltDugum(j).setAyrismaDegeri(j);
        }
        for (int j=0; j<degerNumarasi; j++) {
            ayristirma(dugum.getAltDugum(j));
        }
        dugum.setVeriKumesi(null);
    }
    
    /**
     * Verileri dosyadan oku.
     * @param dosyaIsmi Dosya ismi
     * @throws Exception Okuma hatası
     */
    public void verileriOku(String dosyaIsmi) throws Exception {
        FileInputStream girdi = null;
        try {
            File dosyaIcerigi = new File(dosyaIsmi);
            girdi = new FileInputStream(dosyaIcerigi);
        } catch ( Exception e) {
            System.err.println( "Veri dosyası açılamıyor : " + dosyaIsmi + "\n" + e);
        }
        BufferedReader tampon = new BufferedReader(new InputStreamReader(girdi));
        String okunanVeri;
        while(true) {
            okunanVeri = tampon.readLine();
            if (okunanVeri == null) {
                System.err.println( "Veri dosyasında veriler bulunamadı : " + dosyaIsmi + "\n");
            }
            if (okunanVeri.startsWith("//")) {
                continue;
            }
            if (okunanVeri.equals("")) {
                continue;
            }
            break;
        }
        StringTokenizer siniflandirici = new StringTokenizer(okunanVeri);
        oznitelikNumarasi = siniflandirici.countTokens();
        if (oznitelikNumarasi <= 1) {
            System.err.println( "Okunan Satir : " + okunanVeri);
            System.err.println( "Nitelikleri doğrultusunda isimleri elde edilemedi…");
            System.err.println( "En azından bir giriş öznitelik ve bir çıkış öznitelik bekleniyor…");
        }
        etkiAlanlari = new Vector[oznitelikNumarasi];
        for (int i=0; i < oznitelikNumarasi; i++) { 
            etkiAlanlari[i] = new Vector();
        }
        oznitelikIsimleri = new String[oznitelikNumarasi];
        for (int i=0; i < oznitelikNumarasi; i++) {
            oznitelikIsimleri[i]  = siniflandirici.nextToken();
        }
        while(true) {
            okunanVeri = tampon.readLine();
            if (okunanVeri == null) {
                break;
            }
            if (okunanVeri.startsWith("//")) {
                continue;
            }
            if (okunanVeri.equals("")) {
                continue;
            }
            siniflandirici = new StringTokenizer(okunanVeri);
            int numtokens = siniflandirici.countTokens();
            if (numtokens != oznitelikNumarasi) {
                System.err.println( "Okunan : " + kok.getVeriKumesi().size() + " data");
                System.err.println( "Okunan Son Satir : " + okunanVeri);
                System.err.println( "Beklenen  " + oznitelikNumarasi  + " Degerler");
            }
            Veri nokta = new Veri(oznitelikNumarasi);
            for (int i=0; i < oznitelikNumarasi; i++) {
                nokta.setOznitelikleri(sembolDegeriniGetir(i, siniflandirici.nextToken()), i);
                System.out.println("Veri okuma: Eklenen nokta -> " + nokta.getOznitelikleri(i));
            }
            kok.getVeriKumesi().addElement(nokta);
        }
        tampon.close();
        System.out.println("Veri okuma sona erdi.");
    }
    
    /**
     * Karar ağacını yazdır.
     * @param dugum Düğüm
     * @param sekme Sekme
     */
    public void agaciYazdir(Dugum dugum, String sekme) {
        int degeriDisariAlmakIcin = oznitelikNumarasi-1;
        if (dugum.getAltDugum() == null) {
            int []degerler = butunDegerleriGetir(dugum.getVeriKumesi(), degeriDisariAlmakIcin );
            if (degerler.length == 1) {
                System.out.println(sekme + "\t" + oznitelikIsimleri[degeriDisariAlmakIcin] + " = \"" + etkiAlanlari[degeriDisariAlmakIcin].elementAt(degerler[0]) + "\";");
                return;
            }
            System.out.print(sekme + "\t" + oznitelikIsimleri[degeriDisariAlmakIcin] + " = {");
            for (int i=0; i < degerler.length; i++) {
                System.out.print("\"" + etkiAlanlari[degeriDisariAlmakIcin].elementAt(degerler[i]) + "\" ");
                if ( i != degerler.length-1 ) {
                    System.out.print( " , " );
                }
            }
            System.out.println( " };");
            return;
        }
        int degerNumarasi = dugum.getAltDugum().length;
        for (int i=0; i < degerNumarasi; i++) {
            System.out.println(sekme + "if( " + oznitelikIsimleri[dugum.getAyrismaOzniteligi()] + " == \"" + etkiAlanlari[dugum.getAyrismaOzniteligi()].elementAt(i) + "\") {" );
            agaciYazdir(dugum.getAltDugum(i), sekme + "\t");
            if (i != degerNumarasi-1) {
                System.out.print(sekme +  "} else ");
            }
            else {
                System.out.println(sekme +  "}");
            }
        }
    } 
    
    /**
     * Karar ağacını oluştur.
     */
    public void kararAgaciOlustur() {
        System.out.println("Karar ağacı oluşturuluyor.");
        ayristirma(kok);
        agaciYazdir(kok, "");
    }
    
    /**
     * Karar ağacına göre karar ver.
     * @param dugum Baslangıç düğümü
     * @param sekme Sekme
     * @param vaka Vaka 
     */
    public void kararVer(Dugum dugum, String sekme, Map<String, Cevap> vaka) {
        int degeriDisariAlmakIcin = oznitelikNumarasi-1;
        if (dugum.getAltDugum() == null) {
            int []degerler = butunDegerleriGetir(dugum.getVeriKumesi(), degeriDisariAlmakIcin);
            if (degerler.length == 1) {
                System.out.println(sekme + "\t" + oznitelikIsimleri[degeriDisariAlmakIcin] + " = \"" + etkiAlanlari[degeriDisariAlmakIcin].elementAt(degerler[0]) + "\"");
                Cevap cevap = new Cevap();
                cevap.setTanim(etkiAlanlari[degeriDisariAlmakIcin].elementAt(degerler[0]).toString());
                vaka.put(oznitelikIsimleri[degeriDisariAlmakIcin], cevap);
                return;
            }
            else {
                System.out.println(sekme + "\tTeşhis koyulamadı!");
            }
        }
        int degerNumarasi = dugum.getAltDugum().length;
        if (degerNumarasi == 2) {
            if (vaka.get(oznitelikIsimleri[dugum.getAyrismaOzniteligi()]).getTanim().equals("Evet")) {
                System.out.println(sekme + "Dugum( " + oznitelikIsimleri[dugum.getAyrismaOzniteligi()] + " == \"" + etkiAlanlari[dugum.getAyrismaOzniteligi()].elementAt(0) + "\")" );
                kararVer(dugum.getAltDugum(0), sekme + "\t", vaka);
            }
            else {
                System.out.println(sekme + "Dugum( " + oznitelikIsimleri[dugum.getAyrismaOzniteligi()] + " == \"" + etkiAlanlari[dugum.getAyrismaOzniteligi()].elementAt(1) + "\")" );
                kararVer(dugum.getAltDugum(1), sekme + "\t", vaka);
            }
        }
        else {
            if (vaka.get(oznitelikIsimleri[dugum.getAyrismaOzniteligi()]).getTanim().equals("Yüksek")) {
                System.out.println(sekme + "Dugum( " + oznitelikIsimleri[dugum.getAyrismaOzniteligi()] + " == \"" + etkiAlanlari[dugum.getAyrismaOzniteligi()].elementAt(0) + "\")" );
                kararVer(dugum.getAltDugum(0), sekme + "\t", vaka);
            }
            else if (vaka.get(oznitelikIsimleri[dugum.getAyrismaOzniteligi()]).getTanim().equals("Orta")) {
                System.out.println(sekme + "Dugum( " + oznitelikIsimleri[dugum.getAyrismaOzniteligi()] + " == \"" + etkiAlanlari[dugum.getAyrismaOzniteligi()].elementAt(1) + "\")" );
                kararVer(dugum.getAltDugum(1), sekme + "\t", vaka);
            }
            else {
                System.out.println(sekme + "Dugum( " + oznitelikIsimleri[dugum.getAyrismaOzniteligi()] + " == \"" + etkiAlanlari[dugum.getAyrismaOzniteligi()].elementAt(2) + "\")" );
                kararVer(dugum.getAltDugum(2), sekme + "\t", vaka);
            }
        }
    }
    
    /**
     * Karar ağacına göre. 
     * @param vaka Vaka
     */
    public double kararAl(Map<String, Cevap> vaka) {
        System.out.println("Karar Ağacı ve Karar Verme Süreci");
        kararVer(kok, "", vaka);
        System.out.println("X = " + vaka.get("X").getTanim());
        if (vaka.get("X").getTanim().equals("Evet")) {
            return 1;
        }
        else if (vaka.get("X").getTanim().equals("Yüksek")) {
            return 0.9;
        }
        else if (vaka.get("X").getTanim().equals("Orta")) {
            return 0.6;
        }
        else if (vaka.get("X").getTanim().equals("Düşük")) {
            return 0.3;
        }
        else {
            return 0;
        }
    } 
}
